package wechat

import (
	"context"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"gitee.com/wosylf/ltool"
	"gitee.com/wosylf/ltool/pkg/util"
	"gitee.com/wosylf/ltool/pkg/xhttp"
	"gitee.com/wosylf/ltool/xutil"
)

const (
	TempHour  = 3600    //1小时
	TempDay30 = 2592000 //30天的临时

	V临时字符串 = "QR_STR_SCENE"       //临时字符串
	V临时数字  = "QR_SCENE"           //临时数字
	V永久数字  = "QR_LIMIT_SCENE"     //永久数字
	V永久字符串 = "QR_LIMIT_STR_SCENE" //永久字符串
)

type S上传类型 string

type WxButton struct {
	Type      string      `json:"type"`
	Name      string      `json:"name"`
	Key       string      `json:"key,omitempty"`
	Url       string      `json:"url,omitempty"`
	Appid     string      `json:"appid,omitempty"`
	Pagepath  string      `json:"pagepath,omitempty"`
	MediaId   string      `json:"media_id,omitempty"`
	SubButton []*WxButton `json:"sub_button"`
}

const (
	S上传类型_图片  S上传类型 = "image"
	S上传类型_语音  S上传类型 = "voice"
	S上传类型_视频  S上传类型 = "video"
	S上传类型_缩略图 S上传类型 = "thumb"
)

var (
	ctx = context.Background()
)

func (a *C微信链接) F发送模板消息(temp *TempMessage) (res *WechatRmsg, err error) {
	accessToken, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	res = new(WechatRmsg)
	url := "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken
	_, err = xhttp.NewClient().Post(url).SendStruct(temp).EndStruct(ctx, res)
	//_, err = xhttp.NewClient().doProdPost(url).EndStruct(ctx, userInfo)
	if err != nil {
		return
	}
	if res.Errcode != 0 {
		err = errors.New(res.Errmsg)
	}
	return
}

func (a *C微信链接) F获取Tk与过期时间和锁() (string, int64, *sync.RWMutex) {
	return a.Tk, a.Exp, &a.M
}
func (a *C微信链接) F获取token() (token string, exp int64, err error) {
	accessToken := new(Oauth2AccessToken)
	url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + a.AppId + "&secret=" + a.AppSecret
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken)
	if err != nil {
		return
	}
	a.Tk = accessToken.AccessToken
	a.Exp = time.Now().Unix() + int64(accessToken.ExpiresIn) - 300

	return a.Tk, a.Exp, nil
}

// GetOauth2AccessToken 微信第三方登录，code 换取 access_token
// appId：应用唯一标识，在微信开放平台提交应用审核通过后获得
// appSecret：应用密钥AppSecret，在微信开放平台提交应用审核通过后获得
// code：App用户换取access_token的code
// 文档：https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
func (a *C微信链接) F微信第三方登录(code string) (accessToken *Oauth2AccessToken, err error) {
	accessToken = new(Oauth2AccessToken)
	url := "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + a.AppId + "&secret=" + a.AppSecret + "&code=" + code + "&grant_type=authorization_code"
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken)
	if err != nil {
		return nil, err
	}
	return accessToken, nil
}

// JS-SDK使用权限-签名算法
func (a *C微信链接) F获取网页签名(nonceStr string, ticket string, timeStamp string, url string) (ticketSign string) {
	signStr := fmt.Sprintf("jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s", ticket, nonceStr, timeStamp, url)
	h := sha1.New()
	_, _ = h.Write([]byte(signStr))
	ticketSign = hex.EncodeToString(h.Sum([]byte("")))
	return
}

// getticket,网页端鉴定前需要获取的,这个可以缓存一段时间7200,有效期2小时,后台可以缓存个1个半小时?
// jsapi_ticket是公众号用于调用微信JS接口的临时票据
func (a *C微信链接) F获取JsApiTicket() (wxRsp *TicketInfo, err error) {
	accessToken, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	wxRsp = new(TicketInfo)
	url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi", accessToken)
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, wxRsp)
	if err != nil {
		return
	}
	return
}

func (a *C微信链接) F公众号网页授权(url string) (re map[string]interface{}, err error) {
	nocestr := xutil.F随机字符串(10) //获取10位随机字符串
	tm := xutil.F获取时间戳字符串()
	ticket, _ := a.F获取JsApiTicket() //获取微信的ticket

	re = map[string]interface{}{
		"appId":     a.AppId,
		"beta":      false,
		"debug":     false,
		"nonceStr":  nocestr,
		"signature": a.F获取网页签名(nocestr, ticket.Ticket, tm, url),
		"timestamp": tm,
		"url":       url,
	}
	return
}

// RefreshOauth2AccessToken 刷新微信第三方登录后，获取到的 access_token
// appId：应用唯一标识，在微信开放平台提交应用审核通过后获得
// refreshToken：填写通过获取 access_token 获取到的 refresh_token 参数
// 文档：https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html
func (a *C微信链接) RefreshOauth2AccessToken(refreshToken string) (accessToken *Oauth2AccessToken, err error) {
	accessToken = new(Oauth2AccessToken)
	url := "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + a.AppId + "&grant_type=refresh_token&refresh_token=" + refreshToken
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, accessToken)
	if err != nil {
		return nil, err
	}
	return accessToken, nil
}

// CheckOauth2AccessToken 检验授权凭证（access_token）是否有效
// accessToken：调用接口凭证
// openid：普通用户标识，对该公众帐号唯一，获取 access_token 是获取的
// 文档：https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Authorized_API_call_UnionID.html
func (a *C微信链接) CheckOauth2AccessToken(openid string) (result *CheckAccessTokenRsp, err error) {
	accessToken, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	result = new(CheckAccessTokenRsp)
	url := "https://api.weixin.qq.com/sns/auth?access_token=" + accessToken + "&openid=" + openid
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, result)
	if err != nil {
		return nil, err
	}
	return result, nil
}

// GetOauth2UserInfo 微信开放平台：获取用户个人信息
// accessToken：接口调用凭据，这个跟普通tk不一样，必须为用户登陆的tk
// openId：用户的OpenID
// lang:默认为 zh_CN ，可选填 zh_CN 简体，zh_TW 繁体，en 英语
// 文档：https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Authorized_API_call_UnionID.html
func (a *C微信链接) GetOauth2UserInfo(accessToken, openId string, lang ...string) (userInfo *Oauth2UserInfo, err error) {
	userInfo = new(Oauth2UserInfo)
	url := "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId
	if len(lang) == 1 {
		url += "&lang=" + lang[0]
	}
	_, err = xhttp.NewClient().Get(url).EndStruct(ctx, userInfo)
	if err != nil {
		return nil, err
	}
	return userInfo, nil
}

func (a *C微信链接) F公众号上传永久图片素材(上传类型 S上传类型, file *ltool.File) (media *MediaUpload, err error) {
	accessToken, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	media = new(MediaUpload)
	bm := make(ltool.BodyMap)
	bm.SetFormFile("media", file)
	url := "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=" + accessToken + "&type=" + string(上传类型)
	httpClient := xhttp.NewClient()
	_, err = httpClient.Type(xhttp.TypeMultipartFormData).Post(url).
		SendMultipartBodyMap(bm).EndStruct(ctx, media)
	if err != nil {
		return
	}

	return
}

// 传参第一个为字符串,第二个为类型，默认临时的时间为30天有效
func (a *C微信链接) F创建公众号二维码(scene_str string, args ...string) (resp *PubQr, err error) {
	l := len(args)

	二维码类型 := V临时字符串
	if l == 1 {
		二维码类型 = args[0]
	}
	bm := make(ltool.BodyMap)
	bm.Set("action_name", 二维码类型)
	if 二维码类型 == V临时字符串 { //如果是临时就直接30天有效期
		bm.Set("expire_seconds", 604800)
	}
	bm.SetBodyMap("action_info", func(b ltool.BodyMap) {
		b.SetBodyMap("scene", func(b ltool.BodyMap) {
			b.Set("scene_str", scene_str)
		})
	})
	accessToken, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	resp = new(PubQr)
	url := "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken
	_, err = xhttp.NewClient().Type(xhttp.TypeJSON).Post(url).SendBodyMap(bm).EndStruct(ctx, resp)
	return
}

/*
	 but := []model.WxButton{
		{Type: "media_id", Name: "联系客服", MediaId: media_id},
		//{Type: "miniprogram", Name: "立即推广", Appid: "wx3577a2ab9ddab8f5", Pagepath: "/pages/index/index"},
		{Type: "view", Name: "立即推广", Url: config.V后端域名},
		{SubButton: []model.WxButton{
			{Type: "view", Name: "登入注册", Url: config.V后端域名 + "/pages/users/login/index"},
			//{Type: "miniprogram", Name: "登入注册", Appid: "wx3577a2ab9ddab8f5", Pagepath: "/pages/users/login/index"},
			{Type: "media_id", Name: "关于我们", MediaId: media_id},
		}, Name: "服务中心"},

		//这里再加一个添加图片
	}
*/
func (a *C微信链接) F设置公众号菜单(but []*WxButton) (err error) {
	tk, err := util.F获取缓存token(a)
	if err != nil {
		return
	}
	bm := make(ltool.BodyMap)
	bm.Set("button", but)
	//C微信链接.
	//plug.F打印并且发送消息("v发送的参数", bm.JsonBody())
	url := "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + tk
	_, by, err := xhttp.NewClient().Type(xhttp.TypeJSON).Post(url).
		SendBodyMap(bm).EndBytes(ctx)
	if err != nil {
		return
	}
	//plug.F打印并且发送消息("设置公众号菜单返回的信息是", string(by))
	bm.Reset()
	if err = json.Unmarshal(by, &bm); err != nil {
		return
	}
	if bm.Get("errmsg") != "ok" {
		err = errors.New(bm.Get("errmsg"))
	}
	return
}
